package gateway

import (
	"encoding/json"
	"fmt"
	cb "github.com/hyperledger/fabric/protos/common"
)

type UserContext struct {
	CryptoPath string `json:"cryptoPath"`
	MspID      string `json:"mspid"`
}

func (userCtx UserContext) CryptoSuite() (CryptoSuite, error) {
	return newCryptoSuiteFromLocal(userCtx.CryptoPath, userCtx.MspID)
}

func (userCtx UserContext) CreateChannel(channelID, channelTx string, ordererCtx OrdererContext) error {
	cryptoSuite, err := userCtx.CryptoSuite()
	if err != nil {
		return err
	}
	ordererCli, err := newOrdererClient(ordererCtx.Address, ordererCtx.ServiceName, ordererCtx.TlsRootCert)
	if err != nil {
		return err
	}
	return createChannel(cryptoSuite, ordererCli, channelID, channelTx)
}

func (userCtx UserContext) JoinChannel(ordererCtx OrdererContext, peerCtxs []PeerContext, channelID string) error {
	cryptoSuite, err := userCtx.CryptoSuite()
	if err != nil {
		return err
	}
	ordererCli, err := ordererCtx.client()
	if err != nil {
		return err
	}

	var peerClients []*peerClient
	for _, peerCtx := range peerCtxs {
		client, err := peerCtx.client()
		if err != nil {
			return err
		}
		peerClients = append(peerClients, client)
	}
	return joinChannel(cryptoSuite, ordererCli, channelID, peerClients)
}

func (userCtx UserContext) ListChannel(peerCtx PeerContext) ([]string, error) {
	cryptSuite, err := userCtx.CryptoSuite()
	if err != nil {
		return nil, err
	}
	peerCli, err := peerCtx.client()
	if err != nil {
		return nil, err
	}
	return listChannel(cryptSuite, peerCli)
}

func (userCtx UserContext) GetChannelInfo(peerCtx PeerContext, channelID string) (*BlockChainInfo, error) {
	cryptSuite, err := userCtx.CryptoSuite()
	if err != nil {
		return nil, err
	}
	client, err := peerCtx.client()
	if err != nil {
		return nil, err
	}
	blockInfo, err := getBlockInfoFromChannel(cryptSuite, client, channelID)
	if err != nil {
		return nil, err
	}

	var blockChain BlockChainInfo
	blockChain.Height = blockInfo.Height
	blockChain.CurrentHash = fmt.Sprintf("%x", blockInfo.CurrentBlockHash)
	blockChain.PrevBlockHash = fmt.Sprintf("%x", blockInfo.PreviousBlockHash)
	return &blockChain, nil
}

type BlockInfo struct {
	Height       uint64
	DataHash     string
	PreBlockHash string
}

func (block BlockInfo) JSONPrint() {
	data, err := json.MarshalIndent(block, "", "\t")
	if err != nil {
		fmt.Println("marshal error:", err)
		return
	}
	fmt.Println(string(data))
}

func newBlockInfo(block *cb.Block) *BlockInfo {
	return &BlockInfo{
		Height:       block.Header.Number,
		DataHash:     fmt.Sprintf("%x", block.Header.DataHash),
		PreBlockHash: fmt.Sprintf("%x", block.Header.PreviousHash),
	}
}

func (userCtx UserContext) GetBlockFromChannel(ordererCtx OrdererContext, channelID string, blockNum uint64) (*BlockInfo, error) {
	cryptoSuite, err := userCtx.CryptoSuite()
	if err != nil {
		return nil, err
	}
	ordererCli, _ := ordererCtx.client()
	newestBlock, err := fetchNewestBlockFromChannel(cryptoSuite, ordererCli, channelID)
	if err != nil {
		return nil, err
	}

	if newestBlock.Header.Number >= blockNum {
		return newBlockInfo(newestBlock), nil
	}
	block, err := fetchBlockFromChannel(cryptoSuite, ordererCli, channelID, blockNum)
	if err != nil {
		return nil, err
	}

	return newBlockInfo(block), nil
}

func (userCtx UserContext) InstallChainCode(ccSpec ChaincodeSpec, peerCtxs []PeerContext) error {
	cryptoSuite, err := userCtx.CryptoSuite()
	if err != nil {
		return err
	}

	client, err := peerCtxs[0].client()
	if err != nil {
		return err
	}
	return installChaincode(cryptoSuite, client, ccSpec)
}

func (userCtx UserContext) InstantiatedChainCode(ccSpec ChaincodeSpec, ordererCtx OrdererContext, peerCtxs []PeerContext, channelID string) error {
	cryptoSuite, err := userCtx.CryptoSuite()
	if err != nil {
		return err
	}
	ordererCli, _ := ordererCtx.client()
	peerCli, _ := peerCtxs[0].client()
	return instantiateChaincode(cryptoSuite, ccSpec, ordererCli, peerCli, channelID)
}

func (userCtx UserContext) InvokeChaincode(ccSpec ChaincodeSpec, ordererCtx OrdererContext, peerCtx PeerContext, channelID string) error {
	cryptoSuite, err := userCtx.CryptoSuite()
	if err != nil {
		return err
	}
	ordererCli, _ := ordererCtx.client()
	peerCli, _ := peerCtx.client()
	return invokeChaincode(cryptoSuite, ccSpec, peerCli, ordererCli, channelID)
}

func (userCtx UserContext) QueryChainCode(ccSpec ChaincodeSpec, ordererCtx OrdererContext, peerCtx PeerContext, channelID string) error {
	cryptoSuite, err := userCtx.CryptoSuite()
	if err != nil {
		return err
	}
	ordererCli, _ := ordererCtx.client()
	peerCli, _ := peerCtx.client()
	return queryChainCode(cryptoSuite, ccSpec, peerCli, ordererCli, channelID)
}

func (userCtx UserContext) ListChainCode(peerCtx PeerContext, channelID string, instantiated bool) ([]ChaincodeInfo, error) {
	cryptoSuite, err := userCtx.CryptoSuite()
	if err != nil {
		return nil, err
	}
	peerCli, _ := peerCtx.client()
	return listChaincode(cryptoSuite, peerCli, true, instantiated, channelID)
}

type PeerContext struct {
	Address     string
	ServiceName string
	TlsRootCert string
}

func (peerCtx PeerContext) client() (*peerClient, error) {
	return newPeerCient(peerCtx.Address, peerCtx.ServiceName, peerCtx.TlsRootCert)
}

type OrdererContext struct {
	Address     string
	ServiceName string
	TlsRootCert string
}

func (orderer OrdererContext) client() (*ordererClient, error) {
	return newOrdererClient(orderer.Address, orderer.ServiceName, orderer.TlsRootCert)
}

type CertKey struct {
	sourceFrom string
	bytes      []byte
	path       string
}

func (ck CertKey) GetPath() string {
	return ck.path
}

func (ck CertKey) GetBytes() []byte {
	return ck.bytes
}

type BlockChainInfo struct {
	Height        uint64
	CurrentHash   string
	PrevBlockHash string
}
